import { t } from '@src/locales'
/* components */
import BaseListForNoData from '@src/component/common/BaseListForNoData/index.vue'
/* enum */
import ComponentNameEnum from '@model/enum/ComponentNameEnum'
import StorageModuleEnum from '@model/enum/StorageModuleEnum'
/* filter */
import { formatFormFieldItem } from '@src/filter/form'
/* model */
import { LINK_REG } from '@src/model/reg'
import { TABLE_MAX_CHECKED_COUNT } from '@src/model/const/Number'
import { LINK_START_ERROR_MESSAGE } from '@src/model/const/Alert'
/* service */
import { getFieldName, isSystemFiled } from '@service/FieldService'
/* vue */
import VC from '@model/VC'
import { CreateElement, VNode } from 'vue'
import { Component, Emit, Prop, Ref } from 'vue-property-decorator'
/* scss */
import '@src/component/business/BizTable/BizTable.scss'
/* types */
import Column from '@model/types/Column'
/* util */
import { storageGet, storageSet } from '@src/util/storage.ts'
import { isElement, isEmpty, isFunction } from '@src/util/type'
import Platform from '@src/util/platform'
import * as _ from 'lodash'

import { randomString, useFormTimezone } from 'pub-bbx-utils'

export type BizTableScopeSlot = {
  column: Column,
  value: any
}

// 表格列默认宽度
const TableColumnDefaultWidth = '120px'
// 表格事件枚举
enum BizTableEmitEventEnum {
  ChangeData = 'changeData',
  ChangeColumns = 'changeColumns',
  ChangeSelection = 'changeSelection',
  SortChanged = 'sortChanged'
}

@Component({
  name: ComponentNameEnum.BizTable,
  components:{
    BaseListForNoData
  }
})
class BizTable extends VC {
  
  // @ts-ignore
  @Ref() readonly TableComponent!: Table
  
  /* 表格列 */
  @Prop() readonly columns: Column[] | undefined
  /* 表格数据 */
  @Prop() readonly data: any[] | undefined
  /* 缓存数据key */
  @Prop() readonly storageKey: string | undefined
  /* 排序方式方法 */
  @Prop() readonly sortChangeFunc: Function | undefined
  @Prop() readonly tableContainerHeight: string | undefined
  @Prop() readonly loading: boolean | undefined
  
  @Prop({ 
    default: true 
  }) 
  readonly showTableSelection: boolean | undefined
  
  @Prop({ 
    default: false 
  }) 
  readonly isManualGetSelection: boolean | undefined
  
  /* 已选择项 */
  private multipleSelection: any[] = []
  /* 表格key 随机数 */
  private tableKey: number | string = Math.random() * 1000 >> 2
  
  /* 表格列数据 */
  get tableColumns(): Column[] {
    return this.columns || []
  }
  
  @Emit(BizTableEmitEventEnum.ChangeColumns)
  private emitColumnChangedHandler(columns: Column[]) {
    return columns
  }
  
  @Emit(BizTableEmitEventEnum.ChangeSelection)
  private emitMultipleSelectionChangedHandler(multipleSelection: any[]) {
    return multipleSelection
  }
  
  @Emit(BizTableEmitEventEnum.ChangeData)
  public emitDataChangedHandler(data: any[]): any[] {
    return data
  }
  
  @Emit(BizTableEmitEventEnum.SortChanged)
  private emitSortChangedHandler(option: { prop?: any, order?: any }) {
    return option
  }
  
  
  /**
   * @description 构建多行文本
   * 主要是处理多行文本中的链接显示
   * @return {String} 文本
  */
  public buildTextarea(value: any): string {
    if (isEmpty(value)) return ''
    
    return (
      value.replace(LINK_REG, (match: string) => {
        return `<a href="javascript:;" target="_blank" url="${match}">${match}</a>`;
      })
    )
  }
  
  /** 
   * @description 构建列
  */
  private async buildColumns() {
    // 判断是否存在缓存键名
    if (!this.storageKey) return
    
    // 获取数据，无数据则返回
    let localColumns = await this.getDataToStorage(this.storageKey, [])
    if (localColumns.length <= 0) return
    
    /** 
     * 将列数组转换成对象 
     * column: Column[] -> columnMap: { [Column.field]: Column }
    */
    let columnMap = localColumns.reduce((acc: any, current: Column) => {
      acc[current.field || ''] = current
      return acc
    }, {})
    
    // 取出缓存数据中的显示属性和宽度属性覆盖现有的列数据
    const columns = this.columns?.map((column: Column) => {
      column.show = columnMap[column.field || '']?.show || false
      column.width = columnMap[column.field || '']?.width || undefined
      column.fixLeft = columnMap[column.field || ''].fixLeft && 'left'
      return column
    }) || []
    
    // 提交分发列数据更改事件
    this.emitColumnChangedHandler(columns)
  }
  
  /**
   * @description: 清除表格选择项
   * @return {void}
   */  
  public clearSelection(): void {
    this.TableComponent?.clearSelection()
    this.multipleSelection = []
  }
  
  /**
   * @description: 获取字段值
   * @param {string} fieldName 字段名称
   * @param {any} row 表格行数据
   * @return {String | null}
  */  
  public getValue(fieldName: string | undefined, row: any): string | null {
    return row?.[fieldName || ''] || null
  }
  
  /**
   * @description 从缓存获取数据
  */
  public async getDataToStorage(key: string, data: any) {
    return storageGet(key, data, StorageModuleEnum.Common)
  }
  
  /** 
   * @description 表格拖动事件
  */
  private handlerHeaderDragend(newWidth: number, oldWidth: number, tableColumn: any = {}): void {
    let field: string = tableColumn.property || ''
    let columnList: Column[] = this.columns || []
    let column: Column | null = null
    
    for (let i = 0; i < columnList.length; i++) {
      column = columnList[i]
      if (column.field === field) {
        column.width = newWidth
      }
    }
    
    // 精简列数据
    const columns = this.simplifyTableColumsProperty(columnList)
    // 发出列数据修改事件
    this.emitColumnChangedHandler(columnList)
    // 存在缓存数据key 则保存数据
    this.storageKey && this.saveDataToStorage(this.storageKey, columns)
  }
  
  /** 
   * @description 排序变化
  */
  private handlerTableSortChanged(option: { prop?: any, order?: any } = {}) {
    const { prop, order } = option
    
    // 自定义的排序方式
    if (isFunction(this.sortChangeFunc)) {
      const sortChangeFunc = this.sortChangeFunc as Function
      return sortChangeFunc(prop, order)
    }
    
    this.emitSortChangedHandler(option)
  }
  
  private handleSelection(selection: any[]) {
    
    if (this.isManualGetSelection) {
      return
    }
    
    const tv = this.selectionCompute(selection);
    
    const originalSelected = this.multipleSelection.filter(ms => this.data?.some(cs => cs.id === ms.id))
    const unSelected = this.data?.filter(item => originalSelected.every(oc => oc.id !== item.id)) || []
    
    const tableMaxCheckedCount = TABLE_MAX_CHECKED_COUNT
    
    // 判断是否超出最大选择限制
    if (tv.length > tableMaxCheckedCount) {
      this.$nextTick(() => {
        this.handleSelectionWithMoreLimit(originalSelected, unSelected)
      })
      
      return Platform.alert(t('common.base.tip.choiceLimit', { limit: tableMaxCheckedCount }))
    }
    
    this.multipleSelection = tv
    this.emitMultipleSelectionChangedHandler(this.multipleSelection)
    
  }
  
  private handleSelectionWithMoreLimit(originalSelected: any[], unSelected: any[]) {
    if (originalSelected.length <= 0) {
      return this.TableComponent?.clearSelection()
    }
    
    unSelected.forEach(row => {
      this.TableComponent?.toggleRowSelection(row, false)
    })
  }
  
  private getSelection() {
    return this.TableComponent?.selection || this.multipleSelection || []
  }
  
  /**
   * @description 打开外部链接
   * @param {Object} event 事件对象
  */
  public openOutsideLink(event: any) {
    let url = event.target?.getAttribute('url')
    
    if (!url) return
    if (!/http/gi.test(url)) return Platform.alert(LINK_START_ERROR_MESSAGE)
    
    Platform.openLink(url)
  }
  
  /**
   * @description: 清除表格选择项
   * -- 支持外部调用的
   * @return {void}
   */  
  public outsideClearSelection(): void {
    this.clearSelection()
  }

  /**
   * @description: 获取表格选择项
   * -- 支持外部调用的
   * @return {any[]}
   */
  public outsideGetSelection(): any[] {
    return this.getSelection()
  }
  
  public outsideReRenderTable() {
    this.reRenderTable()
  }
  
  /**
   * @description 保存数据到缓存
  */
  public saveDataToStorage(key: string, data: any) {
    storageSet(key, data, StorageModuleEnum.Common)
  }
  
  /** 
   * @description 精简列属性
  */
  private simplifyTableColumsProperty(columns: Column[]): Column[] {
    return (
      columns.map((column: Column) => ({
        field: column.field,
        show: column.show,
        width: column.width,
        fixLeft: column.fixLeft
      }))
    )
  }
  
  /**
   * @description 计算已选择
   */
  private selectionCompute(selection: any[]): any[] {
    let tv = []
    
    tv = this.multipleSelection.filter(ms => this.data?.every((item: any) => item.id !== ms.id))
    tv = _.uniqWith([...tv, ...selection], _.isEqual)
    
    return tv
  }
  
  /** 
   * @description 渲染表格列
  */
  public renderTableColumnField(h: CreateElement, scope: any, column: Column) {
 
    // 字段名称
    const fieldName = getFieldName(column)
    // 字段值
    const value = column.field ? scope.row[column.field] : ''
    // 属性
    const props: BizTableScopeSlot = { column, value: scope.row }
    
    // 自定义render方法
    if (isFunction(column?.render)) {
      return column.render?.(h, props, this)
    }
    if (isFunction(column?.renderCell)) {
      return column.renderCell?.(h, column, scope.row, this)
    }
    // 渲染具名插槽
    if(this.$slots[fieldName]) {
      return this.$slots[fieldName]
    }
    
    // 渲染作用域插槽
    if(this.$scopedSlots[fieldName]) {
      return this.$scopedSlots?.[fieldName]?.call(this, props)
    }
    
    // 渲染业务列
    const renderColumnWithBusinessValue = this.renderColumnWithBusiness(column, scope.row)
    
    // 已经是元素 直接返回
    if (isElement(renderColumnWithBusinessValue)) {
      return renderColumnWithBusinessValue
    }
    
    return (
      <div class='biz-table-cell'>
        { renderColumnWithBusinessValue }
      </div>
    )
    
  }
  
  /**
   * @description: 渲染业务字段
   * @param {Column} column 列
   * @param {any} row 行数据
   * @return {VNode} 元素
   */  
  public renderColumnWithBusiness(column: Column, row: any): VNode | JSX.Element | string | null {
    return this.renderColumnWithCommon(column, row)
  }
  
  /**
   * @description: 渲染通用字段
   * @param {Column} column 列
   * @param {any} row 行数据
   * @return {VNode} 元素
   */
  public renderColumnWithCommon(column: Column, row: any): VNode {
    // 是否是系统字段
    const isSystem = isSystemFiled(column)
    // 字段类型
    const formType = column.formType || ''
    // 字段名称
    const fieldName = getFieldName(column)
    // 值 (区分是否为系统字段，取值地方不同)
    const value = isSystem ? row[fieldName] : row?.attribute?.[fieldName]

    return (
      <div>
        { formatFormFieldItem(column, value) }
      </div>
    )
  }
  
  public reRenderTable() {
    this.tableKey = randomString()
    // 把选中数据的选中状态再次渲染到表格上面去
    if (this.multipleSelection.length) {
      this.$nextTick(() => {
        this.multipleSelection.forEach(item => {
          // @ts-ignore
          this.data.forEach(v => {
            if (v.id === item.id) {
              // @ts-ignore
              this.$refs.TableComponent.toggleRowSelection(v, true);
            }
          })
        })
      })
    }
  }
  
  /** 
   * @description 渲染表格插入 用于无限加载显示
  */
  public renderTableAppendSlot() {
    return (
      <div class={ this.loading ? 'block-hide' : 'block-show'}>
        { this.renderEmptySlot() }
      </div>
    )
  }
  
  private renderHeaderSlot(h: CreateElement, column: Column) {
    // 如果存在自定义渲染方法则调用
    if (isFunction(column?.renderHeader)) {
      return column.renderHeader?.(h, column, this)
    }
    // 否则返回列名
    return column?.label || ''
  }
  
  private renderEmptySlot() {
    
    if (this.$slots.empty) {
      return this.$slots.empty
    }
    
    if (this.$scopedSlots.empty) {
      return this.$scopedSlots.empty({})
    }
    
    return (
      <BaseListForNoData  notice-msg={t('common.base.tip.noData')}></BaseListForNoData>
    )
  }
  
  mounted() {
    this.buildColumns()
  }
  
  render(h: CreateElement) {
    const { disposeFormListViewTime } = useFormTimezone()
    const showColumns = this.tableColumns.filter((column: Column) => column.show)
    // @ts-ignore
    const tableData = disposeFormListViewTime(this.data, showColumns)

    return (
      <el-table
        border
        class="bbx-normal-list-box"
        data={tableData}
        headerRowClassName='base-table-header-v3'
        rowClassName='base-table-row-v3'
        ref='TableComponent'
        key={ this.tableKey }
        row-key = {(row: { id: any }) => row.id}
        onHeader-dragend={(newWidth: number, oldWidth: number, column: any) => this.handlerHeaderDragend(newWidth, oldWidth, column)}
        onSelect={(selection: any[]) => this.handleSelection(selection)}
        onSelect-all={(selection: any[]) => this.handleSelection(selection)}
        onSort-change={(option: any) => this.handlerTableSortChanged(option)}
        height={this.tableContainerHeight}
        stripe
        v-table-style
        scopedSlots={{ empty: () => this.renderTableAppendSlot() }}
      >
        {this.showTableSelection && (
          <el-table-column
            type='selection'
            width='48'
            align='center'
            reserve-selection={true}
            class-name='select-column'
          />
        )}
        {
          showColumns.map((column: Column) => {
            return (
              <el-table-column
                align={column.align}
                fixed={column.fixLeft || column.fixed || false }
                label={column.label}
                key={column.field}
                width={column.width}
                minWidth={column.minWidth ? `${column.minWidth}px` : TableColumnDefaultWidth}
                prop={column.field}
                resizable={true}
                sortable={column.sortable}
                show-overflow-tooltip
                scopedSlots={{
                  default: (scope: any) => this.renderTableColumnField(h, scope, column)
                }}
              >
                <div slot="header">
                  {this.renderHeaderSlot(h, column)}
                </div>
              </el-table-column>
            )
          })
          
        }
        <div slot='append'></div>
        <div slot="empty">
          <div class={ this.loading ? 'block-hide' : 'block-show'}>
            { this.renderEmptySlot() }
          </div>
        </div>
      </el-table>
    )
  }
}

export default BizTable